﻿namespace NTLib.Core.Reflection
{
    using Pool;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading.Tasks;

    /// <summary>
    /// 
    /// </summary>
    [DataContract]
    public class ObjectDescriptor : PoolItem, IElementDescriptor
    {
        #region Fields

        /// <summary>
        /// The string used to display null informations
        /// </summary>
        public const string NULL_STR = "(null)";
        private const string RECURSION = "Recursion";

        private static readonly ObjectPool<ObjectDescriptor> s_pool = new ObjectPool<ObjectDescriptor>();

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectDescriptor"/> class.
        /// </summary>
        public ObjectDescriptor()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the pool.
        /// </summary>
        public static IPool Pool
        {
            get { return s_pool; }
        }

        /// <summary>
        /// Gets or sets the properties.
        /// </summary>
        [DataMember]
        public List<IElementDescriptor> Properties { get; set; }

        /// <summary>
        /// Gets or sets the name of the assembly qualifier.
        /// </summary>
        /// <value>
        /// The name of the assembly qualifier.
        /// </value>
        [DataMember]
        public string AssemblyQualifiedName { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        [DataMember]
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the name of the type.
        /// </summary>
        [DataMember]
        public string TypeName { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// Adds the value property.
        /// </summary>
        public void AddValueProperty<T>(string name, T value)
        {
            var valueDesc = ValueDescriptor.Pool.Get<ValueDescriptor>();
            valueDesc.Initialize((value == null) ? typeof(T) : value.GetType(), name);
            valueDesc.Value = (value == null) ? NULL_STR : value.ToString();

            if (this.Properties == null)
                this.Properties = new List<IElementDescriptor>();

            this.Properties.Add(valueDesc);
        }

        /// <summary>
        /// Adds the object property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public void AddObjectProperty<T>(string name, T value)
        {
            if (value == null)
            {
                AddValueProperty(name, value);
                return;
            }
            var valueDesc = ObjectDescriptor.ReflectionCreate(value, name);

            if (this.Properties == null)
                this.Properties = new List<IElementDescriptor>();

            this.Properties.Add(valueDesc);
        }

        /// <summary>
        /// Adds the collection property.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public void AddCollectionProperty(string name, object value)
        {
            if (value == null)
            {
                AddValueProperty(name, value);
                return;
            }

            var valueDesc = CollectionDescriptor.ReflectionCreate(value as IEnumerable, name);

            if (this.Properties == null)
                this.Properties = new List<IElementDescriptor>();

            this.Properties.Add(valueDesc);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectDescriptor"/> class.
        /// </summary>
        public void Initialize(Type type, string name)
        {
            this.AssemblyQualifiedName = type.AssemblyQualifiedName;
            this.TypeName = type.Name;
            this.Name = name;
        }

        /// <summary>
        /// Reflections the create.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        public static IElementDescriptor ReflectionCreate(object obj, string name = null)
        {
            return ReflectionCreateInternal(obj, name, new HashSet<object>());
        }

        /// <summary>
        /// Reflections the create.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="name">The name.</param>
        /// <param name="lastObjects">The preview refection.</param>
        /// <returns></returns>
        private static IElementDescriptor ReflectionCreateInternal(object obj, string name, HashSet<object> lastObjects)
        {
            if (obj == null)
                throw new ArgumentNullException(nameof(obj));

            var type = obj.GetType();
            if (obj is IObjectDescriptor)
                return ((IObjectDescriptor)obj).Dump(name);

            if (obj is IEnumerable || obj.GetType().IsArray)
                return CollectionDescriptor.ReflectionCreateInternal(obj as IEnumerable, name, lastObjects);

            ObjectDescriptor desc = s_pool.Get();
            desc.Initialize(type, name);
            try
            {
                desc.Properties = (from prop in type.GetTypeInfo().DeclaredProperties
                                   where prop.CanRead && prop.GetMethod != null
                                   select CreateInternal(prop.GetValue(obj), prop.PropertyType, prop.Name, lastObjects)).ToList();
            }
            catch
            {

            }
            return desc;
        }

        /// <summary>
        /// Creates the specified object.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="objType">Type of the object.</param>
        /// <param name="name">The name.</param>
        public static IElementDescriptor Create(object obj, Type objType = null, string name = null)
        {
            return CreateInternal(obj, objType, name, new HashSet<object>());
        }

        /// <summary>
        /// Creates the internal.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="objType">Type of the object.</param>
        /// <param name="name">The name.</param>
        /// <param name="lastObjects">The last objects.</param>
        internal static IElementDescriptor CreateInternal(object obj, Type objType, string name, HashSet<object> lastObjects)
        {
            if (objType == null && obj != null)
                objType = obj.GetType();

            if (obj == null || objType == null ||
                objType.Equals(typeof(object)) ||
                objType.Equals(typeof(string)) ||
                objType.Equals(typeof(DateTime)) ||
                objType.Equals(typeof(TimeSpan)) ||
                objType.Equals(typeof(Guid)) ||
                objType.Equals(typeof(Uri)) ||
                objType.Equals(typeof(Type)) ||
                objType.Equals(typeof(MemberInfo)) ||
                objType.GetTypeInfo().IsEnum ||
                lastObjects.Contains(obj) ||
                (objType.GetTypeInfo().IsValueType && !objType.GetTypeInfo().DeclaredProperties.Any()))
            {
                var value = ValueDescriptor.Pool.Get<ValueDescriptor>();
                value.Initialize(objType, name);
                if (lastObjects.Contains(obj))
                    value.Value = RECURSION;
                else
                    value.Value = (obj == null) ? NULL_STR : obj.ToString();
                return value;
            }

            lastObjects.Add(obj);

            if (obj is IEnumerable || objType.IsArray)
                return CollectionDescriptor.ReflectionCreateInternal(obj as IEnumerable, name, lastObjects);

            return ReflectionCreateInternal(obj, name, lastObjects);
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return JsonString();
        }

        /// <summary>
        /// Jsons the string.
        /// </summary>
        public string JsonString()
        {
            StringBuilder builder = new StringBuilder();
            JsonString(builder, 0);
            return builder.ToString();
        }

        /// <summary>
        /// Jsons the string.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <param name="lvl">The level.</param>
        /// <param name="displayNull">if set to <c>true</c> [display null].</param>
        public virtual void JsonString(StringBuilder builder, int lvl = 0, bool displayNull = false)
        {
            if (!string.IsNullOrEmpty(Name))
            {
                builder.Append(' ', lvl * 4);
                builder.Append(Name);
                builder.Append(" : ");
            }

            var localProp = Properties;
            if (!displayNull)
            {
                localProp = Properties.Where((prop) =>
                {
                    return !(prop is ValueDescriptor && ((ValueDescriptor)prop).Value == NULL_STR);
                }).ToList();
            }

            builder.Append(' ', lvl * 4);
            builder.Append("{ ");
            if (localProp.Count > 1)
                builder.AppendLine();
            bool first = true;
            IElementDescriptor prev = null;
            foreach (var prop in localProp)
            {
                if (!first)
                {
                    if (prev != null && !(prev is ValueDescriptor))
                        builder.Append(' ', (lvl + 1) * 4);
                    builder.AppendLine(",");
                }
                first = false;
                prop.JsonString(builder, (!(prop is ValueDescriptor) || localProp.Count > 1) ? lvl + 1 : 0, displayNull);
                prev = prop;
            }

            if (localProp.Count > 1)
            {
                builder.AppendLine();
                builder.Append(' ', lvl * 4);
            }
            builder.Append("}");
        }

        /// <summary>
        /// Called when the pool item is reset.
        /// Everything MUST be initialized on this method.
        /// </summary>
        protected override void OnReset()
        {
            this.TypeName = null;
            this.Name = null;
            this.AssemblyQualifiedName = null;

            if (this.Properties != null)
            {
                var props = this.Properties.ToArray();
                this.Properties = null;

                foreach (var prop in props)
                {
                    try
                    {
                        prop.Dispose();
                    }
                    catch
                    {

                    }
                }
            }
        }

        #endregion
    }
}
